package edu.gmu.projects.fireresponder.missionplanner;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import android.content.Context;
import edu.gmu.projects.fireresponder.FirstResponder;
import edu.gmu.projects.fireresponder.environment.Asset;
import edu.gmu.projects.fireresponder.environment.Building;
import edu.gmu.projects.fireresponder.environment.Buildings;
import edu.gmu.projects.fireresponder.environment.Location;
import edu.gmu.projects.fireresponder.environment.Path;
import edu.gmu.projects.fireresponder.environment.Paths;
import edu.gmu.projects.fireresponder.environment.Safespot;
import edu.gmu.projects.fireresponder.environment.Safespots;
import edu.gmu.projects.fireresponder.gui.SituationMap;
import edu.gmu.projects.util.geo.CoordinateCalculator;

public class SituationEnvironment {
	private ObjectMapper mapper = new ObjectMapper();
	private Buildings buildings;
	private Safespots safespots;
	private Paths paths;
	private Set<Asset> assets;
	private FirstResponder self;
	private Set<FirstResponder> team;

	private static SituationEnvironment singleton;

	public static SituationEnvironment getSituationEnvironment(
			InputStream buildingDataStream, InputStream pathDataStream,
			InputStream safeSpotDataStream) {
		if (singleton == null) {
			singleton = new SituationEnvironment(buildingDataStream,
					pathDataStream, safeSpotDataStream);
		}
		return singleton;
	}

	public static SituationEnvironment getSituationEnvironment() {
		return singleton;
	}

	public static SituationEnvironment getSituationEnvironment(Context context) {
		if (singleton == null) {
			try {
				InputStream buildingDataStream = context.getAssets().open(
						"json/building-data.json");
				InputStream pathDataStream = context.getAssets().open(
						"json/path-data.json");
				InputStream safeSpotDataStream = context.getAssets().open(
						"json/safespot-data.json");
				singleton = new SituationEnvironment(buildingDataStream,
						pathDataStream, safeSpotDataStream);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return singleton;
	}

	private SituationEnvironment(final InputStream buildingDataStream,
			final InputStream pathDataStream,
			final InputStream safeSpotDataStream) {

		loadBuildings(buildingDataStream);
		loadPaths(pathDataStream);
		loadSafeSpots(safeSpotDataStream);

		updatePathConnections();
		// System.out.println("Is data valid? " + (verifyData() ? "yes" :
		// "no"));

		assets = new HashSet<Asset>();
		assets.addAll(buildings.values());
		assets.addAll(paths.values());
		assets.addAll(safespots.values());

		if (self == null) {
			self = new FirstResponder("Anonymous");
			self.setLocation(new Location(SituationMap.SITUATION_LAT,
					SituationMap.SITUATION_LNG));
		}

		if (team == null) {
			team = Collections.synchronizedSet(new HashSet<FirstResponder>());
		}
	}

	public FirstResponder getSelf() {
		if (self == null) {
			self = new FirstResponder("Anonymous");
			self.setLocation(new Location(SituationMap.SITUATION_LAT,
					SituationMap.SITUATION_LNG));
		}

		return self;
	}

	public Collection<FirstResponder> getTeam() {
		return team;
	}

	public Collection<FirstResponder> getAllResponders() {
		Collection<FirstResponder> responders = new HashSet<FirstResponder>();

		responders.addAll(team);
		responders.add(self);

		return responders;
	}

	public FirstResponder findResponder(String id) {
		if (self.getId().compareTo(id) == 0)
			return self;
		return findTeamMember(id);
	}

	public FirstResponder findTeamMember(String id) {
		FirstResponder fr = null;

		Iterator<FirstResponder> iter = team.iterator();

		while (iter.hasNext()) {
			FirstResponder currFr = iter.next();

			if (currFr.getId().compareTo(id) == 0) {
				fr = currFr;
				break;
			}
		}

		return fr;
	}

	public FirstResponder addTeamMember(String id, Location location) {
		FirstResponder fr = new FirstResponder(id);
		fr.setLocation(location);
		team.add(fr);

		return fr;
	}

	private void updatePathConnections() {
		for (Path pathA : paths.values()) {
			for (Path pathB : paths.values()) {
				if (areAdjacent(pathA, pathB)) {
					pathA.addAdjacentPath(pathB);
					// pathB.addAdjacentPath(pathA); //pathB will be pathA
					// later...
				}
			}
			for (Building building : buildings.values()) {
				if (areAdjacent(pathA, building)) {
					pathA.addAdjacentBuilding(building);
					building.addConnectedAsset(pathA);
				}
			}
			for (Safespot safeSpot : safespots.values()) {
				if (areAdjacent(pathA, safeSpot)) {
					pathA.addAdjacentSafeSpot(safeSpot);
					safeSpot.addConnectedAsset(pathA);
				}
			}
		}
	}

	private boolean areAdjacent(Path a, Path b) {
		return a.getP1().equals(b.getP1()) || a.getP1().equals(b.getP2())
				|| a.getP2().equals(b.getP1()) || a.getP2().equals(b.getP2());
	}

	private boolean areAdjacent(Path p, Building b) {
		return b.getEntryPoints().contains(p.getP1())
				|| b.getEntryPoints().contains(p.getP2());
	}

	private boolean areAdjacent(Path p, Safespot s) {
		return s.getSafespot().equals(p.getP1())
				|| s.getSafespot().equals(p.getP2());
	}

	public boolean verifyData() {
		Map<Building, Set<Location>> unverifiedBuildings = new HashMap<Building, Set<Location>>();
		Set<Safespot> unverifiedSafeSpots = new HashSet<Safespot>();
		Map<Path, Set<Location>> unverifiedPaths = new HashMap<Path, Set<Location>>();

		for (Building building : buildings.values()) {
			Set<Location> entries = new HashSet<Location>();
			entries.addAll(building.getEntryPoints());
			unverifiedBuildings.put(building, entries);
		}
		for (Path path : paths.values()) {
			Set<Location> points = new HashSet<Location>();
			points.addAll(path.getPoints());
			unverifiedPaths.put(path, points);
		}
		unverifiedSafeSpots.addAll(safespots.values());

		for (Path p : paths.values()) {
			for (Safespot spot : safespots.values()) {
				unverifiedPaths.get(p).removeAll(spot.getPoints());
			}
			unverifiedSafeSpots.removeAll(p.getAdjacentSafeSpots());

			for (Building building : unverifiedBuildings.keySet()) {
				unverifiedPaths.get(p).removeAll(building.getPoints());
				unverifiedBuildings.get(building).removeAll(p.getPoints());
			}
			for (Path otherPath : unverifiedPaths.keySet()) {
				if (otherPath != p) {
					unverifiedPaths.get(otherPath).removeAll(p.getPoints());
				}
			}
		}

		boolean valid = true;
		System.out.println("Disconnected SafePoints: ");
		for (Safespot s : unverifiedSafeSpots) {
			System.out.println("\t" + s.getId() + " at " + s.getSafespot());
			valid = false;
		}
		System.out.println("End Disconnected SafePoints");

		System.out.println("Disconnected Building Entries: ");
		for (Building b : unverifiedBuildings.keySet()) {
			Set<Location> unconnectedEntrances = unverifiedBuildings.get(b);
			if (unconnectedEntrances.size() > 0) {
				valid = false;
				System.out.println("\t" + b.getId() + " at: ");
				for (Location l : unconnectedEntrances) {
					System.out.println("\t\t" + l);
				}
			}
		}
		System.out.println("End Disconnected Buildings Entries");

		System.out.println("Dead-end/Disconnected Paths: ");
		for (Path p : unverifiedPaths.keySet()) {
			Set<Location> unconnectedEndPoint = unverifiedPaths.get(p);
			if (unconnectedEndPoint.size() > 0) {
				valid = false;
				System.out.println("\t" + p.getId() + " at: ");
				for (Location l : unconnectedEndPoint) {
					System.out.println("\t\t" + l);
				}
			}
		}
		System.out.println("End Dead-end/Disconnected Paths");
		return valid;
	}

	private void loadBuildings(InputStream stream) {
		try {
			buildings = mapper.readValue(stream, Buildings.class);
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void loadPaths(InputStream stream) {
		try {
			paths = mapper.readValue(stream, Paths.class);
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void loadSafeSpots(InputStream stream) {
		try {
			safespots = mapper.readValue(stream, Safespots.class);
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public Buildings getBuildings() {
		return buildings;
	}

	public Safespots getSafespots() {
		return safespots;
	}

	public Paths getPaths() {
		return paths;
	}

	public Set<Asset> getAssetSet() {
		Set<Asset> assets = new HashSet<Asset>();
		assets.addAll(buildings.values());
		assets.addAll(paths.values());
		assets.addAll(safespots.values());
		return assets;
	}

	public Map<String, Asset> getAssetMap() {
		Map<String, Asset> assets = new HashMap<String, Asset>();
		assets.putAll(buildings);
		assets.putAll(paths);
		assets.putAll(safespots);
		assets.put(self.getId(), self);
		return assets;
	}

	public Asset getClosestAsset(Location locationIn, Class<?> assetClass) {
		Set<Asset> assets = null;

		if (Building.class == assetClass) {
			assets = new HashSet<Asset>(buildings.values());
		} else if (Safespot.class == assetClass) {
			assets = new HashSet<Asset>(safespots.values());
		} else if (Path.class == assetClass) {
			assets = new HashSet<Asset>(paths.values());
		} else if (FirstResponder.class == assetClass) {
			assets = new HashSet<Asset>(team);
			assets.add(self);
		}

		if (assets != null) {
			return getClosestAsset(locationIn, assets);
		}

		return null;
	}

	public Asset getClosestAsset(Location locationIn) {
		Set<Asset> assets = getAssetSet();
		return getClosestAsset(locationIn, assets);
	}

	public Asset getClosestAsset(Location locationIn, Set<Asset> assets) {
		Asset closestAsset = null;
		double shortestDistance = 0;

		Iterator<Asset> assetIter = assets.iterator();

		// Prime the loop
		if (assetIter.hasNext()) {
			closestAsset = assetIter.next();

			Iterator<Location> points = closestAsset.getPoints().iterator();

			if (points.hasNext()) {
				Location l = points.next();
				shortestDistance = CoordinateCalculator.getDistance(locationIn,
						l);
			}

			while (points.hasNext()) {
				Location location = points.next();

				double distance = CoordinateCalculator.getDistance(locationIn,
						location);

				if (distance < shortestDistance) {
					shortestDistance = distance;
				}
			}
		}

		while (assetIter.hasNext()) {
			Asset asset = assetIter.next();

			Iterator<Location> pointIter = asset.getPoints().iterator();

			while (pointIter.hasNext()) {
				Location location = pointIter.next();
				double distance = CoordinateCalculator.getDistance(locationIn,
						location);

				if (distance < shortestDistance) {
					shortestDistance = distance;
					closestAsset = asset;
				}
			}
		}

		return closestAsset;
	}
}